Skip to content

Conversation

dblythy
Copy link
Member

@dblythy dblythy commented Oct 1, 2025

Pull Request

Issue

parse-community/parse-server#9864

Closes: #2114

Approach

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)

Summary by CodeRabbit

  • New Features

    • Live subscriptions can trigger on-demand queries and receive batched results through a dedicated event.
    • Real-time query results are now emitted to subscribers as fully parsed objects.
  • Tests

    • Added coverage for result event handling and the on-demand query flow in live subscriptions.
  • Documentation

    • Updated TypeScript definitions to reflect the new live subscription API, including the ability to issue queries and receive result events.

Copy link

🚀 Thanks for opening this pull request!

Copy link

coderabbitai bot commented Oct 1, 2025

📝 Walkthrough

Walkthrough

Adds LiveQuery query execution and result delivery: introduces QUERY op and RESULT event in LiveQueryClient, passes client into LiveQuerySubscription, implements subscription.find() to send a query over the WebSocket, and handles RESULT messages by emitting mapped ParseObject results. Updates typings and tests accordingly.

Changes

Cohort / File(s) Summary
LiveQuery client ops & events
src/LiveQueryClient.ts
Adds OP_TYPES.QUERY and OP_EVENTS.RESULT; adds SUBSCRIPTION_EMMITER_TYPES.RESULT; passes client into LiveQuerySubscription; handles RESULT messages by mapping data.results to ParseObject and emitting 'result'; control flow aligned with other cases.
Subscription client plumbing & query API
src/LiveQuerySubscription.ts
Adds optional client field; extends constructor to accept client; implements public find() that awaits client.connectPromise and sends a QUERY op using subscription id as requestId.
Type declarations
types/LiveQuerySubscription.d.ts
Declares client: any; extends constructor signature with client?: any; adds find(): void and docs indicating 'result' delivery.
Tests for result flow and find()
src/__tests__/LiveQueryClient-test.js
Adds tests for RESULT handling emitting two objects; asserts LiveQuerySubscription.prototype.find exists; verifies query message payload sent via subscription.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App as Application
  participant Sub as LiveQuerySubscription
  participant LQC as LiveQueryClient
  participant WS as WebSocket
  participant PS as Parse Server

  App->>Sub: subscription.find()
  alt client attached
    Sub->>LQC: await connectPromise
    Sub->>WS: send QUERY {requestId: sub.id, query,...}
    WS->>PS: QUERY
    PS-->>WS: RESULT {requestId, results:[...]}
    WS-->>LQC: message(OP_EVENTS.RESULT)
    LQC->>Sub: emit('result', [ParseObject,...])
    Sub-->>App: 'result' event with results
  else no client
    Sub-->>App: no-op (find() gated by client)
  end
  note over LQC,Sub: RESULT handling maps payload to ParseObject before emission
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description includes the required sections but the Approach section is empty and the issue link is not properly formatted as a link to the repository’s issue, and the tasks checklist is unchanged despite tests being added, leaving the template incomplete. Please fill out the Approach section with a summary of the code changes, properly link the repository issue in the Issue section, and update the tasks checklist to reflect added tests and any documentation changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feature: add subscription.find" is concise and directly summarizes the primary addition of a new subscription.find method, making it clear to reviewers what the pull request introduces.
Linked Issues Check ✅ Passed The implementation adds the subscription.find method, integrates result event handling on the LiveQueryClient side, and includes tests to verify the feature, thereby fulfilling the primary objective of Alternative 1 from issue #2114.
Out of Scope Changes Check ✅ Passed All modifications are directly related to implementing the subscription.find functionality, associated event handling, and corresponding tests and type declarations, with no unrelated code changes present.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

Copy link

codecov bot commented Oct 1, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (17e2a77) to head (7000301).

Additional details and impacted files
@@            Coverage Diff            @@
##             alpha     #2735   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           63        63           
  Lines         6185      6195   +10     
  Branches      1456      1459    +3     
=========================================
+ Hits          6185      6195   +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
types/LiveQuerySubscription.d.ts (2)

92-92: Consider stronger typing for the client field.

The client field is currently typed as any, which loses type safety. While a direct LiveQueryClient type would create a circular dependency, consider defining a minimal interface or using import type to preserve type information without runtime circularity.

Example approach:

+import type LiveQueryClient from './LiveQueryClient';
+
 declare class LiveQuerySubscription {
     id: string | number;
     query: ParseQuery;
     sessionToken?: string;
     subscribePromise: any;
     unsubscribePromise: any;
     subscribed: boolean;
-    client: any;
+    client?: LiveQueryClient;
     emitter: EventEmitter;
     on: EventEmitter['on'];
     emit: EventEmitter['emit'];
-    constructor(id: string | number, query: ParseQuery, sessionToken?: string, client?: any);
+    constructor(id: string | number, query: ParseQuery, sessionToken?: string, client?: LiveQueryClient);

Also applies to: 96-96


103-107: Enhance JSDoc to document preconditions.

The documentation for find() should clarify what happens when client is not set, and whether callers need to wait for connection before calling.

 /**
  * Execute a query on this subscription.
  * The results will be delivered via the 'result' event.
+ * Note: This method requires a client to be set and will only send the query
+ * after the client's connection promise resolves.
  */
 find(): void;
src/__tests__/LiveQueryClient-test.js (1)

1155-1184: LGTM! Consider adding edge case test.

The test correctly verifies that subscription.find() sends a properly formatted query message with the correct op and requestId. The test appropriately waits for the connection promise before calling find().

Consider adding a test for the edge case where find() is called when client is not set or before connection is established:

it('find() does nothing when client is not set', () => {
  const subscription = new LiveQuerySubscription(1, new ParseQuery('Test'));
  expect(() => subscription.find()).not.toThrow();
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17e2a77 and 7000301.

📒 Files selected for processing (4)
  • src/LiveQueryClient.ts (5 hunks)
  • src/LiveQuerySubscription.ts (2 hunks)
  • src/__tests__/LiveQueryClient-test.js (3 hunks)
  • types/LiveQuerySubscription.d.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/__tests__/LiveQueryClient-test.js (2)
src/__tests__/ParseQuery-test.js (2)
  • LiveQuerySubscription (35-35)
  • ParseQuery (34-34)
src/__tests__/ParseLiveQuery-test.js (2)
  • LiveQuerySubscription (16-16)
  • ParseQuery (15-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (Node 20, 20.15.1)
  • GitHub Check: build (Node 18, 18.20.4)
🔇 Additional comments (8)
src/LiveQueryClient.ts (4)

24-24: LGTM!

The addition of the QUERY operation type follows the existing pattern and is correctly placed.


38-38: LGTM!

The RESULT event type addition is correctly placed and follows the existing pattern.


58-58: LGTM!

The RESULT emitter type addition is correctly placed and follows the existing pattern.


218-218: LGTM!

Passing the client instance to the subscription constructor enables subscription.find() to send messages over the WebSocket. This is essential for the new query execution feature.

src/__tests__/LiveQueryClient-test.js (3)

1-1: LGTM!

Unmocking LiveQuerySubscription and importing it directly is necessary to test the new find() method and verify prototype behavior. This follows the same pattern used in other test files.

Also applies to: 41-41


1096-1132: LGTM!

The test comprehensively verifies that the RESULT event handler correctly maps multiple JSON objects to ParseObject instances and emits them via the subscription. Test structure follows existing patterns.


1134-1136: LGTM!

Both tests appropriately verify that find() exists on the LiveQuerySubscription prototype and on subscription instances. The instance test also confirms the correct type.

Also applies to: 1138-1153

src/LiveQuerySubscription.ts (1)

95-95: LGTM!

The client field and constructor parameter additions are correctly implemented. The client reference is properly stored for use by the find() method.

Also applies to: 103-109

Comment on lines +431 to +437
case OP_EVENTS.RESULT: {
if (subscription) {
const objects = data.results.map(json => ParseObject.fromJSON(json, false));
subscription.emit(SUBSCRIPTION_EMMITER_TYPES.RESULT, objects);
}
break;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add null safety check for data.results.

The RESULT handler assumes data.results is always an array. If the server sends a malformed message without the results field, data.results.map() will throw a runtime error.

Consider adding a defensive check:

 case OP_EVENTS.RESULT: {
   if (subscription) {
+    if (!data.results || !Array.isArray(data.results)) {
+      break;
+    }
     const objects = data.results.map(json => ParseObject.fromJSON(json, false));
     subscription.emit(SUBSCRIPTION_EMMITER_TYPES.RESULT, objects);
   }
   break;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case OP_EVENTS.RESULT: {
if (subscription) {
const objects = data.results.map(json => ParseObject.fromJSON(json, false));
subscription.emit(SUBSCRIPTION_EMMITER_TYPES.RESULT, objects);
}
break;
}
case OP_EVENTS.RESULT: {
if (subscription) {
if (!data.results || !Array.isArray(data.results)) {
break;
}
const objects = data.results.map(json => ParseObject.fromJSON(json, false));
subscription.emit(SUBSCRIPTION_EMMITER_TYPES.RESULT, objects);
}
break;
}
🤖 Prompt for AI Agents
In src/LiveQueryClient.ts around lines 431 to 437, the RESULT branch assumes
data.results is always present and an array; add a defensive null/type check
before calling .map to avoid runtime errors when the server sends malformed
messages. Update the handler to verify data.results is an array (or provide an
empty array fallback) and only map when valid, then emit the subscription result
using the safe array; also consider logging or ignoring invalid payloads so
malformed messages don't throw.

Comment on lines +141 to +150
find() {
if (this.client) {
this.client.connectPromise.then(() => {
this.client.socket.send(JSON.stringify({
op: 'query',
requestId: this.id,
}));
});
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling and consider user feedback for missing client.

The find() method has several potential issues:

  1. Silent failure when client is missing: If this.client is not set, the method does nothing without notifying the caller. This could lead to confusion.
  2. No error handling: If connectPromise rejects or socket.send() throws, errors are swallowed.
  3. No socket existence check: If this.client.socket is undefined, accessing send will throw.

Consider these improvements:

 find() {
   if (this.client) {
     this.client.connectPromise.then(() => {
-      this.client.socket.send(JSON.stringify({
-        op: 'query',
-        requestId: this.id,
-      }));
+      if (this.client.socket) {
+        this.client.socket.send(JSON.stringify({
+          op: 'query',
+          requestId: this.id,
+        }));
+      }
+    }).catch(error => {
+      this.emit('error', error);
     });
+  } else {
+    this.emit('error', new Error('Cannot execute find: client not available'));
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
find() {
if (this.client) {
this.client.connectPromise.then(() => {
this.client.socket.send(JSON.stringify({
op: 'query',
requestId: this.id,
}));
});
}
}
find() {
if (this.client) {
this.client.connectPromise.then(() => {
if (this.client.socket) {
this.client.socket.send(JSON.stringify({
op: 'query',
requestId: this.id,
}));
}
}).catch(error => {
this.emit('error', error);
});
} else {
this.emit('error', new Error('Cannot execute find: client not available'));
}
}
🤖 Prompt for AI Agents
In src/LiveQuerySubscription.ts around lines 141 to 150, the find() method
silently no-ops when this.client is missing, swallows connection/send errors,
and assumes this.client.socket exists; update it to explicitly handle the
missing-client case (either throw an Error or return/reject a Promise with a
clear message so callers get feedback), call
this.client.connectPromise.then(...).catch(...) to handle connection rejection,
and inside the then block check that this.client.socket is present before
calling socket.send (wrap send in try/catch and surface/log the error or reject
the returned Promise). Ensure the method returns a Promise (or consistently
signals failures) so callers can observe errors and add any contextual error
logging or event emission as appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow to run queries through LiveQueryClient (Parse Server issue #9086)
2 participants